﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using Firecracker.Bootstrapper.Interception.Application;
using Firecracker.Bootstrapper.Interception.Intercepts;
using Firecracker.Bootstrapper.Utils;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace FireWeaver.Bootstrapper.Interception.Application
{
  [Export(typeof(IInjector))]
  public class CallInjector : IInjector
  {
    [Import]
    public IInterceptLocator Locator { get; set; }

    public bool ShouldApply(InstructionDefinition definition)
    {
      return definition.Instruction.OpCode == OpCodes.Call;
    }

    public void Apply(InstructionDefinition instructionDefinition)
    {
      var before = instructionDefinition.Instruction.Previous;
      var replacement = GetReplacementCall(instructionDefinition.Instruction);
      if (replacement != null)
      {
        var replacementMethod = instructionDefinition.CallingMethod.Module.Import(replacement);
        var processor = instructionDefinition.CallingMethod.Body.GetILProcessor();

        processor.Replace(instructionDefinition.Instruction, processor.Create(OpCodes.Call, replacementMethod));
         
      }
    }

    private MethodReference GetReplacementCall(Instruction instruction)
    {
      var interceptionTargets = Locator.Locate();

      if (instruction.Operand is MethodReference)
      {
        var call = instruction.Operand as MethodReference;

        var x =  from t in interceptionTargets
                 where call.Name != ".ctor" &&
                       (call.DeclaringType.FullName == t.Target.FullName ||
                        t.Target.GetInteritanceHierarchy().Any(y => y.FullName == call.DeclaringType.FullName))
                 select t;
        if (x.Count() != 1)
          return null;

        var result = from m in x.Single().Replacement.Methods
                     where m.Name == call.Name &&
                           m.Parameters.Count == call.Parameters.Count
                     select m;
        if(result.Count() == 0)
        {
          throw new NoMethodFoundException(x.Single().Replacement, call);
        }
        return result.First();
      }
      return null;
    }
  }
}

